import {
  AmbientLight,
  AnimationMixer,
  BoxGeometry,
  Clock,
  GridHelper,
  LoopOnce,
  LoopRepeat,
  Mesh,
  MeshStandardMaterial,
  PCFSoftShadowMap,
  PerspectiveCamera,
  PointLight,
  PointLightHelper,
  Scene,
  WebGLRenderer,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { ModelUtil } from '../utils/ModelUtil'

export class Canvas {
  private dom: HTMLDivElement
  private scene: Scene
  private camera: PerspectiveCamera
  private renderer: WebGLRenderer
  private gltf?: GLTF
  private handleChange?: (gltf) => void
  pointLight: PointLight
  mixer?: AnimationMixer
  actions: any = {}
  previousAction: any
  activeAction: any
  clock: Clock
  constructor(dom: HTMLDivElement) {
    this.dom = dom
    const { clientWidth: width, clientHeight: height } = dom as HTMLDivElement

    this.clock = new Clock()
    const bgColor = 0x393939

    // 1、创建场景
    this.scene = new Scene()
    this.scene.name = 'BaseScene'

    // 2、创建相机
    this.camera = new PerspectiveCamera(75, width / height, 0.1, 1000)
    this.camera.position.set(10, 10, 10)
    this.camera.name = 'BaseCamera'

    // 3、创建渲染器
    this.renderer = new WebGLRenderer({
      antialias: true, //是否开启反锯齿，设置为true开启反锯齿。
      // alpha: true, //是否可以设置背景色透明。
      logarithmicDepthBuffer: true, //模型的重叠部位便不停的闪烁起来。这便是Z-Fighting问题，为解决这个问题，我们可以采用该种方法
    })
    this.renderer.shadowMap.enabled = true // 渲染器是否开启阴影计算
    this.renderer.shadowMap.type = PCFSoftShadowMap
    this.renderer.setSize(width, height)
    this.renderer.setClearColor(bgColor)

    // 轨道控制器
    const orbitControls = new OrbitControls(this.camera, this.renderer.domElement)

    // 变换控制器
    const transformControls = new TransformControls(this.camera, this.renderer.domElement)
    transformControls.userData.static = true
    this.scene.add(transformControls)

    const gridHelper = new GridHelper(100, 20)
    this.scene.add(gridHelper)

    // 环境光
    const ambient = new AmbientLight(0xcccccc, 0.5)
    ambient.userData.static = true
    this.scene.add(ambient)

    // 点光源
    this.pointLight = new PointLight(0xffffff, 100)
    this.pointLight.userData.static = true
    this.pointLight.castShadow = true
    this.scene.add(this.pointLight)
    /** 点光源辅助器 */
    const pointLightHelper = new PointLightHelper(this.pointLight, 1)
    pointLightHelper.userData.static = true
    this.scene.add(pointLightHelper)

    transformControls.attach(this.pointLight)
    transformControls.addEventListener('dragging-changed', (e) => {
      orbitControls.enabled = !e.value
    })

    transformControls.addEventListener('change', () => {
      pointLightHelper.update()
    })

    // 地面
    const floorGeo = new BoxGeometry(100, 100, 0.1)
    const floorMat = new MeshStandardMaterial({ color: 0x393939, side: 2 })
    const floor = new Mesh(floorGeo, floorMat)
    floor.userData.static = true
    floor.rotation.x = Math.PI / 2
    floor.position.y = -0.05
    floor.receiveShadow = true
    this.scene.add(floor)

    dom.appendChild(this.renderer.domElement)
    this.render()
  }

  /** 帧渲染 */
  render() {
    requestAnimationFrame(this.render.bind(this))
    this.mixer?.update(this.clock.getDelta())
    this.renderer.render(this.scene, this.camera)
  }

  /** 重置场景尺寸 */
  resize() {
    // 更新相机大小
    this.camera.aspect = this.dom.clientWidth / this.dom.clientHeight
    // 更新相机的投影矩阵
    this.camera.updateProjectionMatrix()
    // 更新渲染器大小
    this.renderer.setSize(this.dom.clientWidth, this.dom.clientHeight)
    // 设置系统分辨率
    this.renderer.setPixelRatio(window.devicePixelRatio)
  }

  async loadModel(url) {
    this.gltf = await ModelUtil.loadGltf(url)

    // 初始化模型动作
    this.initAction(this.gltf)

    this.clear()
    // 加阴影
    this.gltf.scene.traverse((child) => {
      if (child instanceof Mesh) {
        child.castShadow = true
      }
    })

    const { width, height, depth } = ModelUtil.getBoundingBox(this.gltf)
    const distance = Math.max(width, height, depth)
    // 设置相机距离
    this.camera.position.set(distance, distance, distance)

    // 设置光源距离
    this.pointLight.position.set(-distance, distance, distance)

    // 加到场景中去
    this.gltf.scene.userData.isModel = true
    this.scene.add(this.gltf.scene)

    // 更新模型后，更新动作列表
    this.handleChange && this.handleChange(this.gltf)
  }

  /** 更新 */
  onChange(fn: (gltf) => void) {
    this.handleChange = fn
  }

  /** 移除物体 */
  removeObject(object) {
    object.traverse((child) => {
      if (object.parent) {
        object.parent.remove(child)
      }
      if (child.isMesh) {
        if (child.geometry) {
          child.geometry.dispose()
        }
        if (child.material) {
          child.material.dispose()
        }
      }
    })

    this.scene.remove(object)
  }

  /** 清空 */
  clear() {
    const object = this.scene.children.find((item) => item.userData.isModel)
    if (object) {
      this.removeObject(object)
    }
  }

  /** 销毁 */
  destroy() {}

  initAction(gltf: GLTF) {
    this.mixer = new AnimationMixer(gltf.scene)
    this.actions = {}
    const animations = gltf.animations
    for (let i = 0; i < animations.length; i++) {
      const clip = animations[i]
      const action = this.mixer.clipAction(clip)
      this.actions[clip.name] = action

      // action.clampWhenFinished = true
      // action.loop = LoopOnce
    }
  }

  fadeToAction(name: string, loop = true) {
    const duration = 0.5
    this.previousAction = this.activeAction
    this.activeAction = this.actions[name]

    if (this.previousAction !== this.activeAction) {
      this.previousAction?.fadeOut(duration)
    }
    if (loop) {
      this.activeAction.loop = LoopRepeat
      this.activeAction.clampWhenFinished = false
    } else {
      this.activeAction.loop = LoopOnce
      this.activeAction.clampWhenFinished = true
    }
    this.activeAction?.reset().setEffectiveTimeScale(1).setEffectiveWeight(1).fadeIn(duration).play()
  }
}
